#include "jbuffer.h"
#include "jabstractio_p.h"
#include <JXQCore/JDebug>

namespace JXQ
{

// 不喜欢面向过程，但是又喜欢面向过程
class J_DECL_HIDDEN JBufferPrivate : public JAbstractIOPrivate
{
	J_DECLARE_PUBLIC(JBuffer)

public:
	explicit JBufferPrivate(JBuffer *q_ptr, int64_t size);
	~JBufferPrivate();

public:
	int check(OpenMode mode, const void *buf, int64_t size);
	int64_t memcpy(void *dec, const void *src, int64_t size);

public:
	std::mutex m_operationMutex;

	int64_t m_pointer = 0;
	int64_t m_size;
	char *m_memory;
};

JBufferPrivate::JBufferPrivate(JBuffer *q_ptr, int64_t size) :
	JAbstractIOPrivate(q_ptr),
	m_size(size),
	m_memory(new char[m_size + 1]) //多加1个保证打印安全
{
	memset(m_memory, 0, size+1);
}

JBufferPrivate::~JBufferPrivate()
{
	delete[] m_memory;
}

int JBufferPrivate::check(OpenMode mode, const void *buf, int64_t size)
{
	m_errNum = 0;

	if( JAbstractIOPrivate::check(mode) == false )
		return -1;

	else if( buf == nullptr )
	{
		m_errNum = InvalidParamete;
		return -1;
	}

	return size <= 0? 0 : 1;
}

int64_t JBufferPrivate::memcpy(void *dec, const void *src, int64_t size)
{
	int available = m_size - m_pointer;
	if( size > available )
		size = available;

	::memcpy(dec, src, size);
	m_pointer += size;

	return size;
}

/*---------------------------------------------------------------------------------------*/

JBuffer::JBuffer(int64_t bufSize) :
	JAbstractIO(new JBufferPrivate(this, bufSize))
{

}

JBuffer::JBuffer(const void *initData, int64_t size, int64_t bufSize) :
	JBuffer(bufSize)
{
	J_D(JBuffer)
	d->memcpy(d->m_memory + d->m_pointer, initData, size);
}

JBuffer::JBuffer(JBufferPrivate *d_ptr) :
	JAbstractIO(d_ptr)
{

}

JBuffer::JBuffer(JBufferPrivate *d_ptr, const void *initData, int64_t size) :
	JAbstractIO(d_ptr)
{
	J_D(JBuffer)
	d->memcpy(d->m_memory + d->m_pointer, initData, size);
}

JBuffer::~JBuffer()
{

}

bool JBuffer::close()
{
	clear();
	return JAbstractIO::close();
}

void JBuffer::setBufferSize(int64_t size)
{
	J_D(JBuffer)
	J_MUTEX_LOCKER(d->m_operationMutex);

	if( size == d->m_size )
		return ;

	if( size < 1 )
		size = 1;

	char *tmp = new char[size];
	if( size < d->m_size )
	{
		if( d->m_pointer > size )
			d->m_pointer = size;
		memcpy(tmp, d->m_memory, size);
	}
	else
		memcpy(tmp, d->m_memory, d->m_size);

	delete d->m_memory;
	d->m_memory = tmp;
	d->m_size = size;
}

int64_t JBuffer::size() const
{
	J_C_D(JBuffer)
	J_MUTEX_LOCKER(d->m_operationMutex);
	return d->m_size;
}

void JBuffer::flush()
{
	J_D(JBuffer)
	J_MUTEX_LOCKER(d->m_operationMutex);

	memset(d->m_memory, 0, d->m_size);
	d->m_pointer = 0;
}

int64_t JBuffer::seek(SeekWhence whence, int64_t offset)
{
	J_D(JBuffer)
	J_MUTEX_LOCKER(d->m_operationMutex);

	int result = 0;

	if( whence == SeekSet )
	{
		if( offset < 0 )
			offset = -offset;
		if( offset > d->m_size )
			offset = d->m_size;

		result = offset - d->m_pointer;
		d->m_pointer = offset;
	}
	else if( whence == SeekCur )
	{
		result = d->m_pointer;
		d->m_pointer += offset;

		if( d->m_pointer > d->m_size )
			d->m_pointer = d->m_size;
		else if( d->m_pointer < 0 )
			d->m_pointer = 0;

		result = d->m_pointer - result;
	}
	else if( whence == SeekEnd )
	{
		if( offset > 0 )
			offset = -offset;
		if( offset > d->m_size )
			offset = d->m_size;

		result = d->m_pointer;
		d->m_pointer = d->m_size - offset;
		result = d->m_pointer - result;
	}
	return result;
}

int64_t JBuffer::tell() const
{
	J_C_D(JBuffer)
	J_MUTEX_LOCKER(d->m_operationMutex);
	return d->m_pointer;
}

const char *JBuffer::data() const
{
	return d_func()->m_memory;
}

char &JBuffer::operator[](int64_t index)
{
	return d_func()->m_memory[index];
}

int64_t JBuffer::virtualRead(int64_t bufSize, void *buffer)
{
	J_D(JBuffer)
	int res = d->check(ReadOnly, buffer, bufSize);
	if( res <= 0 )
		return res;

	J_MUTEX_LOCKER(d->m_operationMutex);
	return d->memcpy(buffer, d->m_memory + d->m_pointer, bufSize);
}

int64_t JBuffer::virtualWrite(int64_t bufSize, const void *buffer)
{
	J_D(JBuffer)
	int res = d->check(WriteOnly, buffer, bufSize);
	if( res <= 0 )
		return res;

	J_MUTEX_LOCKER(d->m_operationMutex);
	return d->memcpy(d->m_memory + d->m_pointer, buffer, bufSize);
}

/*---------------------------------------------------------------------------------------*/

class J_DECL_HIDDEN JQueueBufferPrivate : public JBufferPrivate
{
public:
	using JBufferPrivate::JBufferPrivate;
	int64_t read(void *buffer, int64_t bufSize);

public:
	int64_t m_start = 0;
};

int64_t JQueueBufferPrivate::read(void *buffer, int64_t bufSize)
{
	if( m_start == m_pointer )
		return 0;

	else if( m_start < m_pointer )
	{
		volatile int64_t size = m_pointer - m_start;
		if( bufSize > size )
			bufSize = size;

		::memcpy(buffer, m_memory + m_start, bufSize);
	}
	else
	{
		volatile int64_t frontHarfSize = m_size - m_start;
		volatile int64_t tmpSize = bufSize;

		if( bufSize > frontHarfSize )
		{
			::memcpy(buffer, m_memory + m_start, frontHarfSize);
			tmpSize -= frontHarfSize;
			buffer = J_RCT(char*, buffer) + frontHarfSize;
		}
		else
		{
			::memcpy(buffer, m_memory + m_start, bufSize);
			tmpSize = 0;
			return bufSize;
		}

		volatile int64_t backHarfSize = m_pointer + 1;

		if( tmpSize > backHarfSize )
		{
			::memcpy(buffer, m_memory, backHarfSize);
			tmpSize -= backHarfSize;
		}
		else
		{
			::memcpy(buffer, m_memory, tmpSize);
			tmpSize = 0;
		}
		bufSize -= tmpSize;
	}
	return bufSize;
}

/*---------------------------------------------------------------------------------------*/

JQueueBuffer::JQueueBuffer(int64_t bufSize) :
	JBuffer(new JQueueBufferPrivate(this, bufSize))
{

}

JQueueBuffer::JQueueBuffer(const void *initData, int64_t size, int64_t bufSize) :
	JBuffer(new JQueueBufferPrivate(this, bufSize), initData, size)
{

}

void JQueueBuffer::setBufferSize(int64_t size)
{
	J_D(JQueueBuffer)
	d->m_operationMutex.lock();

	if( size == d->m_size )
		return ;

	char *tmp = new char[d->m_size];
	int64_t res = d->read(tmp, d->m_size);

	delete[] d->m_memory;
	d->m_memory = new char[size];

	d->m_size = size;
	d->m_start = 0;

	if( res > size )
	{
		::memcpy(d->m_memory, tmp + res - size, size);
		d->m_pointer = size;
	}
	else
	{
		::memcpy(d->m_memory, tmp, res);
		d->m_pointer = res;
	}

	d->m_operationMutex.unlock();
	delete tmp;
}

int64_t JQueueBuffer::bytesAvailable() const
{
	J_C_D(JQueueBuffer)
	J_MUTEX_LOCKER(d->m_operationMutex);

	if( d->m_start <= d->m_pointer )
		return d->m_pointer - d->m_start;

	return d->m_size - d->m_start + d->m_pointer + 1;
}

int64_t JQueueBuffer::preread(void *buffer, int64_t bufSize)
{
	J_D(JQueueBuffer)
	J_MUTEX_LOCKER(d->m_operationMutex);
	return d->read(buffer, bufSize);
}

JString JQueueBuffer::preread()
{
	J_D(JQueueBuffer)
	J_MUTEX_LOCKER(d->m_operationMutex);

	auto buffer = std::shared_ptr<char>(new char[d->m_size + 1]);
	memset(buffer.get(), 0, d->m_size + 1);

	d->read(buffer.get(), d->m_size);
	return JString(buffer.get());
}

int64_t JQueueBuffer::find(char c) const
{
	J_C_D(JQueueBuffer)
	J_MUTEX_LOCKER(d->m_operationMutex);

	if( d->m_start < d->m_pointer )
	{
		for(int64_t i=d->m_start; i<d->m_pointer; i++)
		{
			if( d->m_memory[i] == '\n' )
				return i;
		}
	}
	else if( d->m_start > d->m_pointer )
	{
		for(int64_t i=d->m_start; i<d->m_size; i++)
		{
			if( d->m_memory[i] == '\n' )
				return i;
		}
		for(int64_t i=0; i<=d->m_pointer; i++)
		{
			if( d->m_memory[i] == '\n' )
				return i;
		}
	}
	return -1;
}

int64_t JQueueBuffer::rfind(char c) const
{
	J_C_D(JQueueBuffer)
	J_MUTEX_LOCKER(d->m_operationMutex);

	if( d->m_start < d->m_pointer )
	{
		for(int64_t i=d->m_pointer-1; i>=d->m_start; i--)
		{
			if( d->m_memory[i] == '\n' )
				return i;
		}
	}
	else if( d->m_start > d->m_pointer )
	{
		for(int64_t i=d->m_pointer; i>=0; i--)
		{
			if( d->m_memory[i] == '\n' )
				return i;
		}
		for(int64_t i=d->m_size-1; i>=d->m_start; i--)
		{
			if( d->m_memory[i] == '\n' )
				return i;
		}
	}
	return -1;
}

void JQueueBuffer::flush()
{
	JBuffer::flush();
	d_func()->m_start = 0;
}

int64_t JQueueBuffer::bytesWritable() const
{
	J_C_D(JQueueBuffer)
	J_MUTEX_LOCKER(d->m_operationMutex);

	if( d->m_start <= d->m_pointer )
		return d->m_size - d->m_pointer + d->m_start;

	return d->m_pointer - d->m_start;
}

bool JQueueBuffer::isFull() const
{
	J_C_D(JQueueBuffer)
	J_MUTEX_LOCKER(d->m_operationMutex);
	return (d->m_start == 0 and d->m_pointer == d->m_size) or
			d->m_start - d->m_pointer == 1;
}

bool JQueueBuffer::isEmpty() const
{
	J_C_D(JQueueBuffer)
	J_MUTEX_LOCKER(d->m_operationMutex);
	return d->m_start == d->m_pointer;
}

int64_t JQueueBuffer::virtualRead(int64_t bufSize, void *buffer)
{
	J_D(JQueueBuffer)
	int res = d->check(ReadOnly, buffer, bufSize);
	if( res <= 0 )
		return res;

	J_MUTEX_LOCKER(d->m_operationMutex);

	int64_t realSize = d->read(buffer, bufSize);
	d->m_start += realSize;

	if( d->m_start >= d->m_size )
	{
		d->m_start -= d->m_size;
		++d->m_pointer;
	}
	return realSize;
}

int64_t JQueueBuffer::virtualWrite(int64_t bufSize, const void *buffer)
{
	J_D(JQueueBuffer)
	int res = d->check(WriteOnly, buffer, bufSize);
	if( res <= 0 )
		return res;

	J_MUTEX_LOCKER(d->m_operationMutex);
	const char *charBuffer = J_RCT(const char*, buffer);

	if( bufSize >= d->m_size )
	{
		charBuffer += bufSize - d->m_size;
		memcpy(d->m_memory, charBuffer, d->m_size);
		d->m_start = 0;
		d->m_pointer = d->m_size;
		return bufSize;
	}

	volatile int64_t backHarfSize = 0;
	volatile int64_t offset = 0;
	volatile int64_t freeSize = 0;

	if( d->m_start <= d->m_pointer )
	{
		backHarfSize = d->m_size - d->m_pointer;
		freeSize = d->m_start + backHarfSize;
	}
	else
	{
		offset = 1;
		backHarfSize = d->m_size - d->m_pointer - offset;
		freeSize = d->m_start - d->m_pointer - offset;
	}

	if( bufSize > freeSize )
	{
		d->m_start = d->m_start + bufSize - freeSize;
		if( d->m_start >= d->m_size )
			d->m_start -= d->m_size;
	}

	if( backHarfSize >= bufSize )
		memcpy(d->m_memory + d->m_pointer - offset, charBuffer, bufSize);
	else
	{
		memcpy(d->m_memory + d->m_pointer - offset, charBuffer, backHarfSize);
		memcpy(d->m_memory, charBuffer + backHarfSize, bufSize - backHarfSize);
	}

	d->m_pointer += bufSize;
	if( d->m_pointer > d->m_size )
		d->m_pointer -= d->m_size + 1;

	return bufSize;
}

/*---------------------------------------------------------------------------------------*/

JDebug &operator<<(JDebug &d, const JBuffer *jbuffer)
{
	auto c = d.channel();
	if( c == d.StdOut )
		std::cout << "JBuffer(\"" << jbuffer->d_func()->m_memory << "\")";
	else if( c == d.StdErr )
		std::cerr << "JBuffer(\"" << jbuffer->d_func()->m_memory << "\")";
	else if( c == d.Log )
		std::clog << "JBuffer(\"" << jbuffer->d_func()->m_memory << "\")";
	return d;
}

JDebug &operator<<(JDebug &&d, const JBuffer *jbuffer)
{
	return d << jbuffer;
}

JDebug &operator<<(JDebug &d, const JBuffer &jbuffer)
{
	return d << &jbuffer;
}

JDebug &operator<<(JDebug &&d, const JBuffer &jbuffer)
{
	return d << &jbuffer;
}

JDebug &operator<<(JDebug &d, const JQueueBuffer *jbuffer)
{
	auto c = d.channel();
#if 1
	JQueueBufferPrivate *const d_ptr = J_CCT(JQueueBufferPrivate *const, jbuffer->d_func());

	int size = jbuffer->bytesAvailable();
	auto tmp = std::shared_ptr<char>(new char[size + 1]);
	memset(tmp.get(), 0, size+1);

	d_ptr->m_operationMutex.lock();
	d_ptr->read(tmp.get(), size);
	d_ptr->m_operationMutex.unlock();

	if( c == d.StdOut )
		std::cout << "JQueueBuffer(\"" << tmp.get() << "\")";
	else if( c == d.StdErr )
		std::cerr << "JQueueBuffer(\"" << tmp.get() << "\")";
	else if( c == d.Log )
		std::clog << "JQueueBuffer(\"" << tmp.get() << "\")";
#else
	if( c == d.StdOut )
		std::cout << "JQueueBuffer(\"" << jbuffer->d_func()->m_memory << "\")";
	else if( c == d.StdErr )
		std::cerr << "JQueueBuffer(\"" << jbuffer->d_func()->m_memory << "\")";
	else if( c == d.Log )
		std::clog << "JQueueBuffer(\"" << jbuffer->d_func()->m_memory << "\")";
#endif
	return d;
}

JDebug &operator<<(JDebug &&d, const JQueueBuffer *jbuffer)
{
	return d << jbuffer;
}

JDebug &operator<<(JDebug &d, const JQueueBuffer &jbuffer)
{
	return d << &jbuffer;
}

JDebug &operator<<(JDebug &&d, const JQueueBuffer &jbuffer)
{
	return d << &jbuffer;
}

} //namespace JXQ
